package org.lixue.common.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
public class HttpsUtil {
    private static boolean useProxy = true;
    private static HttpClientContext context = null;
    private static String socksKey = "socks.address";
    private static CloseableHttpClient client = null;
    private static int connectTimeout = 2000;//ms
    private static int socketTimeout = 2000;//ms

    static {
        context = HttpClientContext.create();
        if (useProxy) {
            InetSocketAddress socksAddr = new InetSocketAddress("127.0.0.1", 1080);
            context.setAttribute(socksKey, socksAddr);
        }
    }

    private static CloseableHttpClient createSSLClientDefault() {
        try {
            if (client == null) {
                Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", new HttpsUtil.MyConnectionSocketFactory())
                        .register("https", new HttpsUtil.MySSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build();
                PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg, new HttpsUtil.FakeDnsResolver());
                client = HttpClients.custom().setConnectionManager(cm).build();
            }
        } catch (Exception e) {
            log.error("CloseableHttpClient ->exception={}", e);
            client = HttpClients.createDefault();
        }
        return client;
    }


    public static String doGet(String url) {
        log.debug("https doGet->" + url);
        createSSLClientDefault();
        return get(url);
    }

    private static String get(String url) {
        StringBuilder sb = new StringBuilder();
        try {
            HttpGet get = new HttpGet(url);
            // 设置请求和传输超时时间
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
            get.setConfig(requestConfig);
            CloseableHttpResponse resp = client.execute(get, context);
            InputStream in = resp.getEntity().getContent();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            in.close();
            br.close();
            log.debug("https get resp->" + sb.toString());
        } catch (IOException e) {
            log.warn("get exception={}", e);
        }
        return sb.toString();
    }


    public static String doPost(String url, Map<String, String> param) {
        log.debug("https doPost->" + url);
        createSSLClientDefault();
        return post(url, param);
    }

    private static String post(String url, Map<String, String> param) {

        StringBuilder sb = new StringBuilder();
        try {
            // 设置请求和传输超时时间
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
            HttpPost post = new HttpPost(url);
            post.setConfig(requestConfig);
            List<NameValuePair> paramList = new ArrayList<>();
            for (String keys : param.keySet()) {
                paramList.add(new BasicNameValuePair(keys, param.get(keys)));
            }
            post.setEntity(new UrlEncodedFormEntity(paramList));
            CloseableHttpResponse resp = client.execute(post, context);
            InputStream in = resp.getEntity().getContent();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            in.close();
            br.close();
            log.debug("https post resp->" + sb.toString());
        } catch (IOException e) {
            log.warn("post exception={}", e);
        }
        return sb.toString();
    }

    public static CloseableHttpResponse doPost(String url, String body) {
        log.debug("https post-> url: {}, body: {}", url, body);
        createSSLClientDefault();
        try {
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
            HttpPost post = new HttpPost(url);
            post.setConfig(requestConfig);
            post.setEntity(new ByteArrayEntity(body.getBytes()));

            CloseableHttpResponse resp = client.execute(post, context);

            for (Header header : resp.getAllHeaders()) {
                log.debug("header: " + header);
            }
            return resp;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    public static String doPostGetContentString(String url, String body) {
        HttpResponse httpResponse = doPost(url, body);
        try {
            if (httpResponse == null) {
                log.warn("httpResponse is null");
                return null;
            }
            log.debug("contentType={}", httpResponse.getEntity().getContentType());
            log.debug("contentLength={}", httpResponse.getEntity().getContentLength());
            return EntityUtils.toString(httpResponse.getEntity());
        } catch (Exception e) {
            log.warn("getContent error, exception={}", e);
            return null;
        }
    }

    public static class FakeDnsResolver implements DnsResolver {
        @Override
        public InetAddress[] resolve(String host) throws UnknownHostException {
            // Return some fake DNS record for every request, we won't be using it
            return new InetAddress[]{InetAddress.getByAddress(new byte[]{1, 1, 1, 1})};
        }
    }

    public static class MyConnectionSocketFactory extends PlainConnectionSocketFactory {
        @Override
        public Socket createSocket(final HttpContext context) throws IOException {
            InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute(socksKey);
            Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
            return new Socket(proxy);
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
                                    InetSocketAddress localAddress, HttpContext context) throws IOException {
            // Convert address to unresolved
            InetSocketAddress unresolvedRemote = InetSocketAddress
                    .createUnresolved(host.getHostName(), remoteAddress.getPort());
            return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
        }
    }

    public static class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {

        public MySSLConnectionSocketFactory(final SSLContext sslContext) {
            // You may need this verifier if target site's certificate is not secure
            super(sslContext, NoopHostnameVerifier.INSTANCE);
        }

        @Override
        public Socket createSocket(final HttpContext context) throws IOException {
            InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute(socksKey);
            Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
            return new Socket(proxy);
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
                                    InetSocketAddress localAddress, HttpContext context) throws IOException {
            InetSocketAddress unresolvedRemote = InetSocketAddress
                    .createUnresolved(host.getHostName(), remoteAddress.getPort());
            return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
        }
    }
}
